package br.ufal.tci.nexos.arcolive.beans;

import java.io.IOException;
import java.io.Serializable;

import javax.media.ConfigureCompleteEvent;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.DataSink;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.NoProcessorException;
import javax.media.PrefetchCompleteEvent;
import javax.media.Processor;
import javax.media.RealizeCompleteEvent;
import javax.media.ResourceUnavailableEvent;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.FileTypeDescriptor;

import jmapps.util.StateHelper;

public class ScreenGrabber implements Serializable, ControllerListener {

	private int width;

	private int height;

	private int frameRate;
	
	private ContentDescriptor contentDescriptor;

	private MediaLocator outputMediaLocator;

	private Object waitSync = new Object();

	private boolean stateTransitionOK = true;

	private Processor processor;

	/**
	 * 
	 */
	public ScreenGrabber() {
	}

	/**
	 * @return
	 */
	public ContentDescriptor getContentDescriptor() {
		return contentDescriptor;
	}

	/**
	 * @param contentDescriptor
	 */
	public void setContentDescriptor(ContentDescriptor contentDescriptor) {
		this.contentDescriptor = contentDescriptor;
	}

	/**
	 * @return
	 */
	public int getFrameRate() {
		return frameRate;
	}

	/**
	 * @param frameRate
	 */
	public void setFrameRate(int frameRate) {
		this.frameRate = frameRate;
	}

	/**
	 * @return
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * @param height
	 */
	public void setHeight(int height) {
		this.height = height;
	}

	/**
	 * @return
	 */
	public MediaLocator getOutputMediaLocator() {
		return outputMediaLocator;
	}

	/**
	 * @param outputMediaLocator
	 */
	public void setOutputMediaLocator(MediaLocator outputMediaLocator) {
		this.outputMediaLocator = outputMediaLocator;
	}

	/**
	 * @return
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * @param width
	 */
	public void setWidth(int width) {
		this.width = width;
	}

	/**
	 * @return
	 * @throws CannotCaptureScreenException
	 */
	public DataSource captureScreen() {
		ImageDataSource imageDataSource = new ImageDataSource(this.width, this.height,
				this.frameRate);

		return imageDataSource;

	}

	/**
	 * @param imageDataSource
	 */
	public void record(DataSource imageDataSource, int miliSeconds) throws CannotRecordScreenException {
		String message = "Cannot capture screen: ";
		try {
			this.processor = Manager.createProcessor(imageDataSource);
		} catch (NoProcessorException e) {
			throw new CannotRecordScreenException(message + e.getMessage());
		} catch (IOException e) {
			throw new CannotRecordScreenException(message + e.getMessage());
		}
		
		this.processor.addControllerListener(this);

		this.processor.configure();
		this.waitForState(Processor.Configured);

		this.processor.setContentDescriptor(this.contentDescriptor);

		this.processor.realize();
		this.waitForState(Processor.Realized);

		this.processor.start();
		
		DataSource dataSource = this.processor.getDataOutput();
		
		DataSink dataSink;
		if ((dataSink = createDataSink(dataSource)) == null) {
			System.err
					.println("Failed to create a DataSink for the given output MediaLocator: "
							+ this.outputMediaLocator);
		}

		try {
			dataSink.start();
		} catch (IOException e) {
			System.err.println("IO error during processing");
		}

		StateHelper stateHelper = new StateHelper(processor);
		stateHelper.playToEndOfMedia(miliSeconds);
		stateHelper.close();

		try {
			dataSink.close();
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * @param dataSource
	 * @return
	 */
	private DataSink createDataSink(DataSource dataSource) {

		DataSink dsink;

		try {
			dsink = Manager.createDataSink(dataSource, this.outputMediaLocator);
			dsink.open();
		} catch (Exception e) {
			System.err.println("Cannot create the DataSink: " + e);
			return null;
		}
		return dsink;
	}

	/**
	 * @param processor
	 * @param state
	 */
	private void waitForState(int state) {
		synchronized (waitSync) {
			try {
				while (this.processor.getState() < state && stateTransitionOK)
					waitSync.wait();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/* (non-Javadoc)
	 * @see javax.media.ControllerListener#controllerUpdate(javax.media.ControllerEvent)
	 */
	public void controllerUpdate(ControllerEvent evt) {
		if (evt instanceof ConfigureCompleteEvent
				|| evt instanceof RealizeCompleteEvent
				|| evt instanceof PrefetchCompleteEvent) {
			synchronized (waitSync) {
				stateTransitionOK = true;
				waitSync.notifyAll();
			}
		} else if (evt instanceof ResourceUnavailableEvent) {
			synchronized (waitSync) {
				stateTransitionOK = false;
				waitSync.notifyAll();
			}
		}

	}

	public static void main(String args[]) {
		ScreenGrabber imageToMovie = new ScreenGrabber();
		imageToMovie.setFrameRate(1);
		imageToMovie.setHeight(768);
		imageToMovie.setWidth(1024);
		imageToMovie.setContentDescriptor(new ContentDescriptor(
				FileTypeDescriptor.MSVIDEO));
		imageToMovie
				.setOutputMediaLocator(new MediaLocator("teste.avi"));
		DataSource ds;
		try {
			ds = imageToMovie.captureScreen();
			imageToMovie.record(ds, 5000);
		} catch (CannotRecordScreenException e) {
			e.printStackTrace();
		}
	}
}